🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / specs / 20260507-161758-node-list-layout / data-model.md
Displaying Raw • View rendered • Download
specs/20260507-161758-node-list-layout/data-model.md bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 4.60 KB
Tc9d1d9# Data Model — Node List Layout
Tc9d1d9## Overview
The Node List Layout feature does not introduce new database entities. It adds **preference keys** to DataStore and a **density enum** that controls how existing Ta5d6ff`Node` model data is rendered. The data model is entirely read-only from the layout perspective — node data comes from the existing Room KMP pipeline.
Tc9d1d9## Entity Relationship
Ta5d6ff```Ta5d6ffmermaid
Ta5d6fferDiagram
DataStore ||--o{ NodeListLayoutPreferences : stores
NodeListLayoutPreferences ||--|| NodeListDensity : selects
NodeListDensity ||--|| NodeItem : "renders (COMPLETE)"
NodeListDensity ||--|| NodeItemCompact : "renders (COMPACT)"
NodeItemCompact ||--o{ CompactToggle : "visibility driven by"
NodeItem }o--|| Node : reads
NodeItemCompact }o--|| Node : reads
Node ||--|| NodeEntity : "backed by"
Ta5d6ff```
Tc9d1d9## Density Enum
Ta5d6ff```Ta5d6ffkotlin
Tff7b72package T7ee787org.meshtastic.feature.node.model
Tff7b72enum Tff7b72class T56d364NodeListDensity Tb4b4b4{
Te6edf3COMPLETETb4b4b4,
Te6edf3COMPACTTb4b4b4;
Tb4b4b4}
Ta5d6ff```
Tff7b72- Persisted as a Ta5d6ff`String` (enum name) in DataStore under key Ta5d6ff`nodeListDensity`.
Tff7b72- Default: Ta5d6ff`COMPLETE`.
Tc9d1d9## Preference Keys
Ta5d6ff```Ta5d6ffkotlin
Tff7b72package T7ee787org.meshtastic.core.prefs.ui
Tff7b72enum Tff7b72class T56d364NodeListLayoutPreferencesTb4b4b4(Tff7b72val Te6edf3keyTb4b4b4: Tffa657StringTb4b4b4, Tff7b72val Te6edf3defaultValueTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3SHOW_POWERTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowPowerTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_LAST_HEARDTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowLastHeardTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3LAST_HEARD_RELATIVETb4b4b4(Ta5d6ff"Ta5d6fflastHeardIsRelativeTa5d6ff"Tb4b4b4, Tff7b72falseTb4b4b4)Tb4b4b4,
Te6edf3SHOW_LOCATIONTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowLocationTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_HOPSTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowHopsTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_SIGNALTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowSignalTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_CHANNELTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowChannelTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_ROLETb4b4b4(Ta5d6ff"Ta5d6ffshouldShowRoleTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4,
Te6edf3SHOW_TELEMETRYTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowTelemetryTa5d6ff"Tb4b4b4, Tff7b72trueTb4b4b4)Tb4b4b4;
Tb4b4b4}
Ta5d6ff```
Tc9d1d9### Preference Access Pattern
Each key is exposed as a Ta5d6ff`StateFlow<Boolean>` in Ta5d6ff`UiPrefsImpl`:
Ta5d6ff```Ta5d6ffkotlin
Tff7b72val Te6edf3shouldShowPowerTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72> Tff7b72= Te6edf3dataStoreTb4b4b4.Te6edf3data
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTff7b72[Te6edf3booleanPreferencesKeyTb4b4b4(Ta5d6ff"Ta5d6ffshouldShowPowerTa5d6ff"Tb4b4b4)Tff7b72] Tff7b72?: Tff7b72true Tb4b4b4}
Tb4b4b4.Te6edf3stateInTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3SharingStartedTb4b4b4.Te6edf3EagerlyTb4b4b4, Tff7b72trueTb4b4b4)
Ta5d6ff```
The density preference follows the same pattern but maps to the enum:
Ta5d6ff```Ta5d6ffkotlin
Tff7b72val Te6edf3nodeListDensityTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3NodeListDensityTff7b72> Tff7b72= Te6edf3dataStoreTb4b4b4.Te6edf3data
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3prefs Tff7b72-Tff7b72>
Tff7b72val Te6edf3name Tff7b72= Te6edf3prefsTff7b72[Te6edf3stringPreferencesKeyTb4b4b4(Ta5d6ff"Ta5d6ffnodeListDensityTa5d6ff"Tb4b4b4)Tff7b72] Tff7b72?: Ta5d6ff"Ta5d6ffCOMPLETETa5d6ff"
Te6edf3NodeListDensityTb4b4b4.Te6edf3valueOfTb4b4b4(Te6edf3nameTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3stateInTb4b4b4(Te6edf3scopeTb4b4b4, Te6edf3SharingStartedTb4b4b4.Te6edf3EagerlyTb4b4b4, Te6edf3NodeListDensityTb4b4b4.Te6edf3COMPLETETb4b4b4)
Ta5d6ff```
Tc9d1d9## Node Data Fields Used by Layout
The layout reads from the existing Ta5d6ff`Node` model in Ta5d6ff`core:model`. No new fields are added.
| Field | Type | Used By | Condition |
|-------|------|---------|-----------|
| Ta5d6ff`longName` | Ta5d6ff`String?` | Both | Always shown |
| Ta5d6ff`shortName` | Ta5d6ff`String?` | Both | NodeChip avatar |
| Ta5d6ff`lastHeard` | Ta5d6ff`Long` | Both | Non-zero, not > 1 year future |
| Ta5d6ff`hopsAway` | Ta5d6ff`Int` | Both | Ta5d6ff`> 0` for hop count, Ta5d6ff`== 0` for signal |
| Ta5d6ff`snr` | Ta5d6ff`Float` | Both | Ta5d6ff`!= 0` and Ta5d6ff`!viaMqtt` |
| Ta5d6ff`rssi` | Ta5d6ff`Int` | Both | Signal quality via Ta5d6ff`determineSignalQuality(snr, rssi)` |
| Ta5d6ff`batteryLevel` | Ta5d6ff`Int?` | Both | Non-null |
| Ta5d6ff`channel` | Ta5d6ff`Int` | Both | Ta5d6ff`> 0` |
| Ta5d6ff`position` | Ta5d6ff`Position?` | Both | Non-null, valid lat/lon |
| Ta5d6ff`role` | Ta5d6ff`DeviceRole` | Both | Always (defaults to 0) |
| Ta5d6ff`viaMqtt` | Ta5d6ff`Boolean` | Both | Signal exclusion gate |
| Ta5d6ff`isFavorite` | Ta5d6ff`Boolean` | Both | Star icon |
| Ta5d6ff`hasPositionLog` | Ta5d6ff`Boolean` | Both | Log icon visibility |
| Ta5d6ff`hasEnvironmentLog` | Ta5d6ff`Boolean` | Both | Log icon visibility |
| Ta5d6ff`hasDetectionSensorLog` | Ta5d6ff`Boolean` | Both | Log icon visibility |
| Ta5d6ff`hasTracerouteLog` | Ta5d6ff`Boolean` | Both | Log icon visibility |
| Ta5d6ff`hasDeviceMetricsLog` | Ta5d6ff`Boolean` | Both | Log icon visibility |
Tc9d1d9## Adaptive Chip Sizing
The compact Ta5d6ff`NodeChip` size is derived from a Ta5d6ff`lineCount` property:
Ta5d6ff```Ta5d6ffkotlin
Tff7b72val Te6edf3lineCountTb4b4b4: Tffa657Int Tff7b72= Te6edf3buildList Tb4b4b4{
Te6edf3addTb4b4b4(T79c0ff1Tb4b4b4) T8b949e// Row 1: name — always present
Tff7b72if Tb4b4b4(Te6edf3shouldShowLastHeardTb4b4b4) Te6edf3addTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3shouldShowLocation Tff7b72|Tff7b72| Te6edf3shouldShowHops Tff7b72|Tff7b72| Te6edf3shouldShowSignal Tff7b72|Tff7b72|
Te6edf3shouldShowChannel Tff7b72|Tff7b72| Te6edf3shouldShowRole Tff7b72|Tff7b72| Te6edf3shouldShowTelemetryTb4b4b4) Te6edf3addTb4b4b4(T79c0ff1Tb4b4b4)
Tb4b4b4}Tb4b4b4.Te6edf3size
Tff7b72val Te6edf3chipSizeTb4b4b4: Te6edf3Dp Tff7b72= Te6edf3maxTb4b4b4(T79c0ff3T79c0ff6.Te6edf3dpTb4b4b4, Te6edf3minTb4b4b4(T79c0ff7T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff2T79c0ff4.Te6edf3dp Tff7b72* Te6edf3lineCountTb4b4b4)Tb4b4b4)
Ta5d6ff```
| lineCount | Chip Size | Active Rows |
|-----------|-----------|-------------|
| 1 | 36.dp | Name only |
| 2 | 48.dp | Name + last heard OR Name + combined |
| 3 | 70.dp | Name + last heard + combined |
Tc9d1d9## Validation Rules
Tff7b72- Ta5d6ff`nodeListDensity` must be a valid Ta5d6ff`NodeListDensity` enum name. Invalid values fall back to Ta5d6ff`COMPLETE`.
Tff7b72- Ta5d6ff`lastHeardIsRelative` is functionally irrelevant when Ta5d6ff`shouldShowLastHeard` is Ta5d6ff`false` (the UI disables the toggle).
Tff7b72- All boolean preferences default to Ta5d6ff`true` except Ta5d6ff`lastHeardIsRelative` which defaults to Ta5d6ff`false`.
Tff7b72- The layout never writes to Ta5d6ff`Node` data — all mutations flow through the existing packet processing pipeline.
Served by rngit 1.5.4 - Generated in 0.05s